/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package io.milton.mail;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.Header;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*/
public class StandardMessageFactoryImpl implements StandardMessageFactory {
private final static Logger log = LoggerFactory.getLogger(StandardMessageFactoryImpl.class);
@Override
public void toStandardMessage(MimeMessage mm, StandardMessage sm) {
try {
sm.setFrom(findFromAddress(mm));
sm.setReplyTo(findReplyTo(mm));
sm.setSubject(findSubject(mm));
sm.setDisposition(mm.getDisposition());
sm.setEncoding(mm.getEncoding());
sm.setContentLanguage(findContentLanguage(mm.getContentLanguage()));
sm.setTo(findRecips(mm, RecipientType.TO));
sm.setCc(findRecips(mm, RecipientType.CC));
sm.setBcc(findRecips(mm, RecipientType.BCC));
sm.setSize(mm.getSize());
Map<String, String> headers = findHeaders(mm);
sm.setHeaders(headers);
Object o = mm.getContent();
if (o instanceof String) {
String text = (String) o;
log.debug("text: " + text);
sm.setText(text);
} else if (o instanceof MimeMultipart) {
MimeMultipart multi = (MimeMultipart) o;
populateMultiPart(multi, sm);
} else {
log.warn("Unknown content type: " + o.getClass() + ". expected string or MimeMultipart");
}
} catch (IOException ex) {
throw new RuntimeException(ex);
} catch (MessagingException ex) {
throw new RuntimeException(ex);
}
}
protected void populateMultiPart(MimeMultipart multi, StandardMessage sm) throws IOException, MessagingException {
log.debug("populateMultiPart: content type: " + multi.getContentType());
for (int i = 0; i < multi.getCount(); i++) {
BodyPart bp = multi.getBodyPart(i);
String disp = bp.getDisposition();
if ((disp != null) && (disp.equals(Part.ATTACHMENT) || disp.equals(Part.INLINE))) {
addAttachment(sm, bp);
} else {
String ct = bp.getContentType();
if (ct.contains("html")) {
if (sm.getHtml() == null) {
sm.setHtml("");
}
String s = sm.getHtml() + getStringContent(bp);
sm.setHtml(s);
} else if (ct.contains("text")) {
if (sm.getText() == null) {
sm.setText("");
}
String s = sm.getText() + getStringContent(bp);
sm.setText(s);
} else if (ct.contains("multipart")) {
Object subMessage = bp.getContent();
if (subMessage instanceof MimeMultipart) {
MimeMultipart child = (MimeMultipart) subMessage;
StandardMessage smSub;
if (ct.contains("related") || ct.contains("alternative") || ct.contains("mixed")) { // accumulate content into current message
smSub = sm;
} else { // otherwise, treat it as an attached message
smSub = sm.instantiateAttachedMessage();
sm.getAttachedMessages().add(smSub);
}
populateMultiPart(child, smSub);
} else {
log.warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!11 unknown sub message type");
}
} else {
addAttachment(sm, bp);
}
}
}
}
protected void addAttachment(StandardMessage sm, BodyPart bp) {
InputStream in = null;
try {
String name = bp.getFileName();
if (name == null) {
name = System.currentTimeMillis() + "";
}
String ct = bp.getContentType();
log.debug("attachment content type: " + ct);
String[] contentIdArr = bp.getHeader("Content-ID");
String contentId = null;
if (contentIdArr != null && contentIdArr.length > 0) {
contentId = contentIdArr[0];
contentId = Utils.parseContentId(contentId);
}
in = bp.getInputStream();
sm.addAttachment(name, ct, contentId, bp.getDisposition(), in);
} catch (IOException ex) {
throw new RuntimeException(ex);
} catch (MessagingException ex) {
throw new RuntimeException(ex);
} finally {
Utils.close(in);
}
}
Map<String, String> findHeaders(MimeMessage mm) {
try {
Map<String, String> map = new HashMap<String, String>();
Enumeration en = mm.getAllHeaders();
while (en.hasMoreElements()) {
Object o = en.nextElement();
Header header = (Header) o;
map.put(header.getName(), header.getValue());
}
return map;
} catch (MessagingException ex) {
throw new RuntimeException(ex);
}
}
void fillRecipients(List<MailboxAddress> to, Address[] recipients) {
if (recipients == null) {
return;
}
for (Address a : recipients) {
MailboxAddress ma = MailboxAddress.parse(a.toString());
to.add(ma);
}
}
void fillContentLanguage(String contentLanguage, MimeMessage mm) {
try {
if (contentLanguage == null) {
return;
}
String[] arr = {contentLanguage};
mm.setContentLanguage(arr);
} catch (MessagingException ex) {
throw new RuntimeException(ex);
}
}
protected void fillContent(StandardMessage sm, Part message) throws MessagingException {
System.out.println("StandardMessageFactoryImpl - fillContent");
if (isText(sm)) {
if (isHtml(sm)) {
if (hasAttachments(sm)) {
// mixed, then alternate, related for html
MimeMultipart multipart = new MimeMultipart("mixed");
message.setContent(multipart);
addTextAndHtmlToMime(multipart, sm);
addAttachmentsToMime(multipart, sm);
} else {
log.debug("text and html. no attachments");
addTextAndHtmlToMime(message, sm);
}
} else if (hasAttachments(sm)) {
// just text and attachments
MimeMultipart multipart = new MimeMultipart("mixed");
message.setContent(multipart);
addTextToMime(multipart, sm);
addAttachmentsToMime(multipart, sm);
} else {
// no html, no attachments
message.setContent(sm.getText(), "text/plain; charset=utf-8");
}
} else if (isHtml(sm)) {
if (hasAttachments(sm)) {
// no text, but has html and attachments, so must do mixed
MimeMultipart multipart = new MimeMultipart("mixed");
message.setContent(multipart);
addHtmlToMime(multipart, sm);
addAttachmentsToMime(multipart, sm);
} else {
// html only, no text or attachments
addHtmlToMime(message, sm);
}
} else if (hasAttachments(sm)) {
// only attachments
MimeMultipart multipart = new MimeMultipart("mixed");
message.setContent(multipart);
addAttachmentsToMime(multipart, sm);
} else {
// no text, no html, no attachments - no content
message.setContent("", "text/plain; charset=utf-8");
}
}
protected boolean isText(StandardMessage sm) {
return sm.getText() != null && sm.getText().length() > 0;
}
protected boolean isHtml(StandardMessage sm) {
return sm.getHtml() != null && sm.getHtml().length() > 0;
}
protected boolean hasAttachments(StandardMessage sm) {
return (sm.getAttachments() != null && sm.getAttachments().size() > 0) || (sm.getAttachedMessages() != null && sm.getAttachedMessages().size() > 0);
}
protected void fillReplyTo(StandardMessage sm, MimeMessage mm) {
try {
MailboxAddress ma = sm.getReplyTo();
if (ma == null) {
return;
}
Address[] addresses = new Address[1];
addresses[0] = ma.toInternetAddress();
mm.setReplyTo(addresses);
} catch (MessagingException ex) {
throw new RuntimeException(ex);
}
}
private void addAttachmentToMime(MimeMultipart multipart, Attachment att) throws MessagingException {
System.out.println("StandardMessageFactoryImpl - addAttachmentToMime2 - " + att.getContentId());
MimeBodyPart bp = new MimeBodyPart();
DataSource fds = new AttachmentReadingDataSource(att);
bp.setDataHandler(new DataHandler(fds));
bp.setHeader("Content-ID", att.getContentId());
bp.setDisposition(att.getDisposition());
bp.setFileName(att.getName());
multipart.addBodyPart(bp);
}
/**
* Adds non inline attachments to the multiparts
*
* @param multipart
* @param sm
*/
private void addAttachmentsToMime(MimeMultipart multipart, StandardMessage sm) throws MessagingException {
System.out.println("StandardMessageFactoryImpl - addAttachmentsToMime1");
if (sm.getAttachments() != null && sm.getAttachments().size() > 0) {
for (Attachment att : sm.getAttachments()) {
if (!isInline(att)) {
System.out.println("StandardMessageFactoryImpl - addAttachmentToMime1");
addAttachmentToMime(multipart, att);
} else {
System.out.println("StandardMessageFactoryImpl - is inline so ignore");
}
}
}
if (sm.getAttachedMessages() != null && sm.getAttachedMessages().size() > 0) {
for (StandardMessage smAttached : sm.getAttachedMessages()) {
MimeBodyPart bp = new MimeBodyPart();
fillContent(smAttached, bp);
multipart.addBodyPart(bp);
}
}
}
private void addHtmlToMime(MimeMultipart multipart, StandardMessage sm) throws MessagingException {
BodyPart bp = new MimeBodyPart();
multipart.addBodyPart(bp);
addHtmlToMime(bp, sm);
}
private void addHtmlToMime(Part part, StandardMessage sm) throws MessagingException {
// need to use a javax.activation.DataSource (!) to set a text
// with content type "text/html"
// part.setDataHandler(new DataHandler(
// new DataSource() {
// public InputStream getInputStream() throws IOException {
// return new ByteArrayInputStream(encoding != null ? text.getBytes(encoding) : text.getBytes());
// }
// public OutputStream getOutputStream() throws IOException {
// throw new UnsupportedOperationException("Read-only javax.activation.DataSource");
// }
// public String getContentType() {
// return "text/html";
// }
// public String getName() {
// return "text";
// }
// }
// ));
List<Attachment> htmlInline = findInlineAttachments(sm);
if (htmlInline == null || htmlInline.isEmpty()) {
part.setContent(sm.getHtml(), "text/html; charset=\"utf-8\"");
} else {
MimeMultipart related = new MimeMultipart("related");
part.setContent(related);
BodyPart bpHtml = new MimeBodyPart();
bpHtml.setHeader("Content-Type","text/plain; charset=\"utf-8\"");
bpHtml.setContent(sm.getHtml(), "text/plain; charset=\"utf-8\"");
related.addBodyPart(bpHtml);
for (Attachment att : htmlInline) {
addAttachmentToMime(related, att);
}
}
}
private void addTextAndHtmlToMime(MimeMultipart multipart, StandardMessage sm) throws MessagingException {
MimeMultipart alternate = createTextAndHtml(sm);
MimeBodyPart bpAlternate = new MimeBodyPart();
bpAlternate.setContent(alternate);
multipart.addBodyPart(bpAlternate);
}
private void addTextAndHtmlToMime(Part message, StandardMessage sm) throws MessagingException {
MimeMultipart alternate = createTextAndHtml(sm);
message.setContent(alternate);
}
private MimeMultipart createTextAndHtml(StandardMessage sm) throws MessagingException {
MimeMultipart alternate = new MimeMultipart("alternative");
MimeBodyPart bpAlternate = new MimeBodyPart();
bpAlternate.setContent(alternate);
addTextToMime(alternate, sm);
addHtmlToMime(alternate, sm);
return alternate;
}
private void addTextToMime(MimeMultipart multipart, StandardMessage sm) throws MessagingException {
BodyPart bp = new MimeBodyPart();
bp.setContent(sm.getText(), "text/plain; charset=utf-8");
multipart.addBodyPart(bp);
}
private void fillBCC(List<MailboxAddress> bcc, MimeMessage mm) {
fillRecipients(bcc, mm, RecipientType.BCC);
}
private void fillCC(List<MailboxAddress> cc, MimeMessage mm) {
fillRecipients(cc, mm, RecipientType.CC);
}
private void fillRecipients(List<MailboxAddress> list, MimeMessage mm, RecipientType type) {
for (MailboxAddress ma : list) {
try {
mm.addRecipient(type, ma.toInternetAddress());
} catch (MessagingException ex) {
throw new RuntimeException(ex);
}
}
}
private void fillTo(List<MailboxAddress> to, MimeMessage mm) {
fillRecipients(to, mm, RecipientType.TO);
}
/**
*
* @param sm
* @return - a list of attachments which have a non empty Content-ID header
*/
private List<Attachment> findInlineAttachments(StandardMessage sm) {
if (sm.getAttachments() == null) {
return null;
}
List<Attachment> list = new ArrayList<Attachment>();
for (Attachment att : sm.getAttachments()) {
if (isInline(att)) {
list.add(att);
}
}
return list;
}
private MailboxAddress findReplyTo(MimeMessage mm) {
try {
return findSingleAddress(mm.getReplyTo());
} catch (MessagingException ex) {
throw new RuntimeException(ex);
}
}
MailboxAddress findFromAddress(MimeMessage mm) {
try {
return findSingleAddress(mm.getFrom());
} catch (MessagingException ex) {
throw new RuntimeException(ex);
}
}
MailboxAddress findSingleAddress(Address[] addresses) {
if (addresses == null || addresses.length == 0) {
return null;
}
return MailboxAddress.parse(addresses[0].toString());
}
String findContentLanguage(String[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
return arr[0];
}
String findSubject(MimeMessage mm) {
try {
return mm.getSubject();
} catch (MessagingException ex) {
throw new RuntimeException(ex);
}
}
String getStringContent(BodyPart bp) {
String text;
try {
Object o2 = bp.getContent();
if (o2 == null) {
text = "";
} else if (o2 instanceof String) {
text = (String) o2;
} else {
log.warn("Unknown content type: " + o2.getClass());
text = o2.toString();
}
log.debug("getStringContent: " + text);
return text;
} catch (IOException ex) {
throw new RuntimeException(ex);
} catch (MessagingException ex) {
throw new RuntimeException(ex);
}
}
List<MailboxAddress> findRecips(MimeMessage mm, RecipientType type) {
try {
Address[] recips = mm.getRecipients(type);
List<MailboxAddress> list = new ArrayList<MailboxAddress>();
if (recips != null) {
for (Address a : recips) {
MailboxAddress mba = MailboxAddress.parse(a.toString());
list.add(mba);
}
}
return list;
} catch (MessagingException ex) {
throw new RuntimeException(ex);
}
}
public MimeMessage toMimeMessage(StandardMessage sm, Session session) {
MimeMessage mm = new MimeMessage(session);
toMimeMessage(sm, mm);
return mm;
}
@Override
public void toMimeMessage(StandardMessage sm, MimeMessage mm) {
//System.out.println("StandardMessageFactoryImpl - toMimeMessage");
try {
//mm.setS
mm.setFrom(sm.getFrom().toInternetAddress());
mm.setSender(sm.getFrom().toInternetAddress());
fillReplyTo(sm, mm);
fillTo(sm.getTo(), mm);
fillCC(sm.getCc(), mm);
fillBCC(sm.getBcc(), mm);
//mm.setSubject(sm.getSubject(), "utf-8");
mm.setSubject(sm.getSubject());
mm.setDisposition(sm.getDisposition());
fillContentLanguage(sm.getContentLanguage(), mm);
fillContent(sm, mm);
// todo: set headers?
} catch (MessagingException ex) {
throw new RuntimeException(ex);
}
}
private boolean isInline(Attachment att) {
return att.getContentId() != null && att.getContentId().length() > 2;
}
public class AttachmentReadingDataSource implements DataSource {
final Attachment att;
public AttachmentReadingDataSource(Attachment att) {
this.att = att;
}
@Override
public InputStream getInputStream() throws IOException {
System.out.println("AttachmentReadingDataSource - getInputStream - " + att.getName());
return att.getInputStream();
}
@Override
public OutputStream getOutputStream() throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public String getContentType() {
log.debug("attachment conte type: " + att.getContentType());
return att.getContentType();
}
@Override
public String getName() {
return att.getName();
}
}
}